<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2019 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1); //声明代码的执行指令为开启严格模式,主要为参数类型为严格模式,方便常驻内存

namespace think\route\dispatch;

use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use think\App;
use think\exception\ClassNotFoundException;
use think\exception\HttpException;
use think\helper\Str;
use think\route\Dispatch;

// +----------------------------------------------------------------------
// | 所属: 路由调度
// | 继承: 路由调度基础类 think\route\Dispatch
// | (愿景,目的) 名称: 控制器调度处理
// | 功能:
// | (心智)原则: 单一职责原则/里氏替换原则/依赖倒置原则/接口隔离原则/迪米特法则/开闭原则
// | 结构组成: 
// | (形式) 类模式: 
// | (模式)模式类型:  
// | (模式)设计模式:  
// | 产生事件:
// | Understand:Cluster | Cluster Call Butterfly
// |
// | ---------分析--------------
// | 需求的产生 想法
// | 道: <用户-客户立场>|<人的视角>(欲望)
// | 愿景:  
// | 目的:  
// |
// | =============================
// | 需求的分解--架构-偏商业架构/业务角度
// | 制定设置规则的原则
// | 法: <组织内部立场>|<人的视角>
// | 如何适应新的应用场景
// | 适用场景:
// | 目的: 
// | 心智:遵循思维模式,原则
// | 结构:
// | 协议: 
// |
// | =============================
// | 术:<组织内部立场>|<物的视角>
// | 用户/客户立场:(人的视角)(结果)感知
// | 形式:定义了整个项目的文件目录形式,各目录文件功能
// |
// | 组织内部立场:(物的视角)(设定)系统思考.逻辑思考
// | 结构:
// |
// | 协议:
// |
// | =============================
// | 方案实施 执行 怎么用
// | 器:<物的视角>
// | 对什么场景下的哪个业务节点.有什么资源方法和模式可用
// | (转换接口,把物和人联系起来)
// | 形式:
// |
// | (用户/客户立场|视角|(背后是结构)
// | 模式:
// |
// | (具体方法)
// | 事件:
// | -------------------------
// |
// | 知识点: 
// | 继承类说明: 
// | 点评:  
// |
// +----------------------------------------------------------------------

/**
 * Controller Dispatcher
 * 控制器调度处理
 */
class Controller extends Dispatch
{
    /**
     * 控制器名
     * @var string
     */
    protected $controller;

    /**
     * 操作名
     * @var string
     */
    protected $actionName;

    /**
     * 初始
     */
    public function init(App $app)
    {
        parent::init($app);

        $result = $this->dispatch;

        if (is_string($result)) {
            $result = explode('/', $result);
        }

        // 获取控制器名
        $controller = strip_tags($result[0] ?: $this->rule->config('default_controller'));

        // 格式化统一风格
        // 下划线转驼峰(首字母大写)
        // 多级
        if (strpos($controller, '.')) {
            $pos              = strrpos($controller, '.');
            $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1));
        } else {
            // 普通方式的
            $this->controller = Str::studly($controller);
        }

        // 获取操作名
        $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action'));

        // 设置当前请求的控制器、操作
        $this->request
            ->setController($this->controller)
            ->setAction($this->actionName);
    }

    /**
     * 真正执行的部分,继承父类,方便调用
     */
    public function exec()
    {
        // 获取当前请求的控制器对象实例
        try {
            // 实例化控制器
            $instance = $this->controller($this->controller);
        } catch (ClassNotFoundException $e) {
            throw new HttpException(404, 'controller not exists:' . $e->getClass());
        }

        // 利用反射机制注册控制器中间件
        // 注册控制器中间件
        $this->registerControllerMiddleware($instance);

        // 执行控制器方法以及前后置中间件
        return $this->app->middleware->pipeline('controller')
            ->send($this->request)
            ->then(function () use ($instance) {
                // 获取当前操作名
                $suffix = $this->rule->config('action_suffix'); //操作方法后缀
                $action = $this->actionName . $suffix;

                if (is_callable([$instance, $action])) {
                    $vars = $this->request->param();
                    try {
                        $reflect = new ReflectionMethod($instance, $action);
                        // 严格获取当前操作方法名
                        $actionName = $reflect->getName();
                        if ($suffix) {
                            $actionName = substr($actionName, 0, -strlen($suffix));
                        }

                        $this->request->setAction($actionName);
                    } catch (ReflectionException $e) {
                        $reflect = new ReflectionMethod($instance, '__call');
                        $vars    = [$action, $vars];
                        $this->request->setAction($action);
                    }
                } else {
                    // 操作不存在
                    throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()');
                }

                // 调用反射执行类的方法
                $data = $this->app->invokeReflectMethod($instance, $reflect, $vars);

                return $this->autoResponse($data);
            });
    }

    /**
     * 使用反射机制注册控制器中间件
     * @access public
     * @param object $controller 控制器实例
     * @return void
     */
    protected function registerControllerMiddleware($controller): void
    {
        // 检查是否加载了中间件middleware其实也就是有这个命名空间路径没
        $class = new ReflectionClass($controller);

        if ($class->hasProperty('middleware')) {
            $reflectionProperty = $class->getProperty('middleware');
            $reflectionProperty->setAccessible(true);

            $middlewares = $reflectionProperty->getValue($controller);

            foreach ($middlewares as $key => $val) {
                if (!is_int($key)) {
                    if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) {
                        return strtolower($item);
                    }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) {
                        continue;
                    } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) {
                        return strtolower($item);
                    }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) {
                        continue;
                    } else {
                        $val = $key;
                    }
                }

                if (is_string($val) && strpos($val, ':')) {
                    $val = explode(':', $val);
                    if (count($val) > 1) {
                        $val = [$val[0], array_slice($val, 1)];
                    }
                }

                $this->app->middleware->controller($val);
            }
        }
    }

    /**
     * 实例化访问控制器
     * @access public
     * @param string $name 资源地址
     * @return object
     * @throws ClassNotFoundException
     */
    public function controller(string $name)
    {
        // 是否使用控制器后缀
        $suffix = $this->rule->config('controller_suffix') ? 'Controller' : '';

        // 访问控制器层名称
        $controllerLayer = $this->rule->config('controller_layer') ?: 'controller';
        // 空控制器名
        $emptyController = $this->rule->config('empty_controller') ?: 'Error';

        // 解析应用类的类名
        $class = $this->app->parseClass($controllerLayer, $name . $suffix);

        // __construct 方法在下面执行时被默认加载
        if (class_exists($class)) {
            return $this->app->make($class, [], true);
        } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) {
            return $this->app->make($emptyClass, [], true);
        }

        throw new ClassNotFoundException('class not exists:' . $class, $class);
    }
}
